【Clojure入門】 Clojureとは
Scalaがオブジェクト指向プログラミングと関数型プログラミングを組み合わせることを選択したのに対し、Clojureは関数型プログラミングのほうをより強く志向してオブジェクト指向プログラミングからは一定の距離を置くようなスタンスを取っています。
なぜ開発言語としてClojureを選ぶのか
なぜClojureを使うのか、Clojureは何を目指している言語なのかを理解するには登場背景を知るのが良いでしょう。
Clojure公式サイトのRationale (日本語版)にClojureを開発する背景となった基本的な考え方がまとまっています。 以下ではClojureの言語としての設計思想と特徴を簡単に見ていきましょう。
Lisp
他の主要なJVM言語と比べても特異な特徴はLispであることでしょう。
Clojureは、Lispとしての良き伝統を引き継ぐ一方で現代的なより良いLispを目指して設計されています。
LispのS式で構成されたコードがそのままデータとしても扱えること(同図像性)は"code as data"(データとしてのコード)と表現されますが、S式の統一的で極めて単純なシンタックスとそれを言語機能をフル活用して自在に変換できる強力なマクロシステムはClojureでも健在です。 また、「REPL駆動開発」(REPL-driven development)と呼ばれるエディタと連携したREPLを活用した高速なフィードバックサイクルによる開発スタイルも特徴的です。
Clojureは主に以下の点で従来の主要なLisp方言と違いがあります。
デフォルトでイミュータブル(不変)であり、状態変更を制限された方法でしか認めない関数型言語
丸括弧 ( ) だけでなく角括弧 [ ] や波括弧 { } を目的によって使い分ける
言語標準でベクター(e.g. [1 2 3])、セット(e.g. #{1 2 3})、マップ(e.g. {:a 1 :b 2 :c 3})などのリテラル表現を提供している
連結リストという具体的な実装ではなく ISeq 、 Sequential 、 Associative など少数の共通の抽象の上にデータ構造を構築して拡張可能にしている
関数型プログラミング
Clojureでは関数がファーストクラスオブジェクトであるのはもちろん、変数やデータ構造はデフォルトでイミュータブルであり、関数型プログラミングを基本とする関数型言語として設計されています。
Haskellのような純粋関数型言語ではなく、評価戦略がデフォルトで遅延評価の言語でもありませんが、遅延シーケンス(lazy sequence; 要素が遅延評価されるシーケンス)を多用する点も特徴的です(例えば最も基本的な標準ライブラリ関数 map も遅延シーケンスを返します)。
また、型クラスに近い一種のポリモーフィズムを実現する「プロトコル」(protocol)、Common LispのCLOSにおける総称関数(generic function)に似た「マルチメソッド」(multimethod)といった機能も備えています。
原則として変数の再代入やデータ構造の破壊的変更は認められないため、再帰やそれを抽象化した高階関数を通してデータ変換のパイプラインとしてプログラムを組み立てていきます。
ScalaやOCaml、Haskellなどとは異なり動的型付けであり、代数的データ型やパターンマッチング、モナディックプログラミングなどは言語としてサポートされていません(※ ライブラリレベルで近い機能を実現しているものはあります: e.g. core.match, funcool/cats)。 Clojureにおいても、関数型プログラミングの重視する不変性/参照透過性/純粋性は並行/並列プログラミングの基盤として大きな意味があります。
オブジェクト指向プログラミング
Clojureは関数型プログラミングを強力に後押しする一方で、JavaやScalaとは大きく異なりオブジェクト指向プログラミングを積極的にはサポートしていません。
関数型プログラミングとオブジェクト指向プログラミングは本質的には概念として直交するものであり、両方の良さを引き出すように言語を設計することも可能と思われますが、Clojureでは
It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures.
というAlan J. Perlisの表現に象徴されるように、少数の抽象としてのデータに対して多数の関数があることを志向し、データと関数を結び付けるのではなく明確に分離しています。
オブジェクト指向言語でよく見られるクラスとメンバー(フィールドとメソッド)、継承といった概念や機能も、ClojureではJavaとの相互運用時を除いて基本的に登場しません。
オブジェクト指向プログラミングで一般的な継承によるポリモーフィズムはClojureではサポートされていませんが、プロトコルやマルチメソッドという異なる形でJavaよりも柔軟にポリモーフィズムを実現することができます。
ちなみに、Rich Hickeyの有名なプレゼンテーションSimple Made Easyでは、オブジェクト、メソッド、継承などが"simple"ではない("complect"した)ものの例として挙げられています。 Javaとの互換性
Clojureでは、従来のLisp方言が直面してきた困難を踏まえて広く使われるプラットフォーム上の言語として設計されたという背景もあり、当初からJVM言語としてJavaとの相互運用性が非常に重視されてきました。
Clojure/Lispとして自然な形でJavaライブラリを直接利用することができ、境界面でどうしてもグルーコードが必要となる場合にもマクロなどの機能を活用してボイラープレートコードを避けることができます。
Lispと聞くとREPLのイメージからかインタプリタとして逐次解釈実行されると思われることもあるようですが、ClojureではREPLであれ事前(AOT)コンパイル時であれ常にJavaバイトコードにコンパイルされます。
動的型付けの言語であるためJavaやScalaと比べるとパフォーマンス面では多少劣りますが、高性能で安定したJVMとそのエコシステムの恩恵を受けることができます。
非同期プログラミング、並行・分散プログラミング
Clojureでは非同期プログラミングをサポートする標準機能としてAgent、 future などがあります。
また、並行プログラミングにおいて可変状態を安全に扱うための参照型としてAtomやRefといったものも標準提供されています。